package com.droidski.taskdb;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.droidski.taskdb.cli.Main;
import com.droidski.taskdb.model.Status;
import com.droidski.taskdb.model.Task;
import com.droidski.taskdb.model.impl.TaskImpl;

public class TaskDB implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 6703032574221545937L;

	private static transient final long VERSION = 1;

	private transient boolean isVerbose = false;

	/**
	 * the publicly visible version
	 */
	private static final transient String PUBLIC_VERSION = "1.0.0";

	/**
	 * the file used to persist to
	 */
	private transient File file;
	
	public static transient TaskDB instance;

	/**
	 * the root task
	 */
	private Task baseTask;
	
	/**
	 * the task the user wants to anchor the view from
	 */
	private Task currentRootTask;
	
	/**
	 * the incrementing task id whenever one is created
	 */
	private int lastTaskId = 0;

	/**
	 * the time in millis this was created
	 */
	private long dateCreated;

	/**
	 * the last save time
	 */
	private long dateLastModified;
	
	private Map<String, Task> tasks;

	public TaskDB() {
		dateCreated = System.currentTimeMillis();
		tasks = new HashMap<String, Task>();
		instance = this;
	}
	
	public void reload() throws Exception {
		if ( dateLastModified != getFile().lastModified() ) {
			load(getFile());
			Main.out(Util.formatDate(System.currentTimeMillis()) + " : Reloaded " + getFile());
		}
	}
	
	public static TaskDB getInstance() {
		return instance;
	}

	public void setFile(File file) {
		this.file = file;
		this.dateLastModified = file.lastModified();
	}

	public File getFile() {
		return file;
	}

	public Task createTask(String title) {
		TaskImpl task = new TaskImpl();
		task.setName(title);
		task.setId("" + (++lastTaskId));
		task.setStatus(Status.NOT_STARTED, "Created");
		task.setLocation(System.getProperty("user.dir"));
		tasks.put(task.getId(), task);
		if ( getCurrentRootTask() != null ) {
			getCurrentRootTask().addTask(task);
		}
		return task;
	}
	
	/**
	 * permanently removes a task, if it has children they are moved to the parent
	 * @param task
	 */
	public void deleteTask(Task taskToRemove) {
		Task parent = taskToRemove.getParent();
		if ( parent != null ) {
			parent.removeTask(taskToRemove);
		}
		List<Task> toRemove = new ArrayList<Task>();
		for ( String childId : taskToRemove.getChildren() ) {
			toRemove.add(findTaskById(childId));
		}
		for ( Task child : toRemove ) {
			if ( parent != null ) {
				parent.addTask(child);
			} else {
				child.setParent(null);
			}
		}
		tasks.remove(taskToRemove.getId());
	}

	public Task getBaseTask() {
		return baseTask;
	}

	public Task getCurrentRootTask() {
		return currentRootTask;
	}

	public void setCurrentRootTask(Task rootTask) {
		this.currentRootTask = rootTask;
	}

	public void setBaseTask(Task task) {
		this.baseTask = task;
		this.currentRootTask = baseTask;
	}

	public static File findFile() {
		// look for environment variable
		// look in home
		String envVar = System.getenv().get("TASK_FILE");
		if (envVar != null) {
//			System.out.println("using TASKDB_FILE " + envVar);
			return new File(envVar);
		} else {
			File homeFile = new File(System.getProperty("user.home") + File.separator + ".taskdb");
//			System.out.println("Using " + homeFile);
			return homeFile;
		}
		
	}

	public static String getVersion() {
		return PUBLIC_VERSION;
	}

	public int getLastTaskId() {
		return lastTaskId;
	}

	public void setLastTaskId(int lastTaskId) {
		this.lastTaskId = lastTaskId;
	}

	public static TaskDB init(File file) throws Exception {
		TaskDB taskDB = new TaskDB();
		taskDB.setFile(file);
		taskDB.setBaseTask(taskDB.createTask("Projects"));
		taskDB.save();
		return taskDB;
	}

	public Task getFirstTask(Task task, TaskProcessor processor) {
		boolean result = processor.process(task);
		if (result)
			return task;
		if (task.getChildren().size() > 0) {
			for (String childId : task.getChildren()) {
				Task child = findTaskById(childId);
				return getFirstTask(child, processor);
			}
		}
		return null;
	}

	public void getAllTasks(Task task, TaskProcessor processor,
			List<Task> results) {
		boolean result = processor.process(task);
		if (result) {
			results.add(task);
		}
		if (task.getChildren().size() > 0) {
			for (String childId : task.getChildren()) {
				Task child = findTaskById(childId);
				getAllTasks(child, processor, results);
			}
		}
	}

	public interface TaskProcessor {
		public boolean process(Task task);
	}

	public Task findTaskById(final String id) {
		return tasks.get(id);
	}

	public long getDateLastModified() {
		return dateLastModified;
	}

	public long getDateCreated() {
		return dateCreated;
	}

	public int getSize() {
		return tasks.size();
	}

	public Task getActiveTask() {
		for ( Task task : tasks.values() ) {
			if ( task.getStatus() == Status.ACTIVE ) {
				return task;
			}
		}
		return null;
	}

	public static TaskDB load() throws IOException, ClassNotFoundException {
		File file = TaskDB.findFile();
		return load(file);
	}

	public static TaskDB load(File file) throws IOException,
			ClassNotFoundException {
		if (!file.exists()) {
			throw new FileNotFoundException(file.toString());
		}

		TaskDB db;
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
		db = (TaskDB) ois.readObject();
		db.setFile(file);
		ois.close();
		return db;
	}

	public void save() throws IOException {
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
				file));
		oos.writeObject(this);
		oos.flush();
		oos.close();
		dateLastModified = file.lastModified();
	}

	private void readObject(ObjectInputStream in)
			throws ClassNotFoundException, IOException {
		// always perform the default de-serialization first
		long thisVersion = in.readLong();
		if (thisVersion == VERSION) {
			baseTask = (Task)in.readObject();
			currentRootTask = (Task)in.readObject();
			lastTaskId = in.readInt();
			dateCreated = in.readLong();
			dateLastModified = in.readLong();
			tasks = (Map<String, Task>)in.readObject();
			
		}
		instance = this;
	}

	private void writeObject(ObjectOutputStream out)
			throws IOException {
		// perform the default serialization for all non-transient, non-static
		// fields
		out.writeLong(VERSION);
		out.writeObject(baseTask);
		out.writeObject(currentRootTask);
		out.writeInt(lastTaskId);
		out.writeLong(dateCreated);
		out.writeLong(dateLastModified);
		out.writeObject(tasks);
		
	}
	
	public Map<String, Task> getTasks() {
		return tasks;
	}
	
	public boolean isVerbose() {
		return isVerbose;
	}
	
	public void setVerbose(boolean verbose) {
		this.isVerbose = verbose;
	}

	public static void out(String msg) {
		if ( instance.isVerbose() ) {
			System.out.println(msg);
		}
	}
	
	public static void err(String msg) {
		System.err.println(msg);
	}

	public List<Task> searchForTasks(final String searchFor) {
		List<Task> hits = new ArrayList<Task>();
		for ( Task task : getTasks().values() ) {
			if (task.getSearchScore(searchFor) > 0 ) {
				hits.add(task);
			}
		}
		
		// sort in order of relevance
		if ( hits.size() > 1 ) {
			Collections.sort(hits, new Comparator<Task>() {

				@Override
				public int compare(Task t1, Task t2) {
					int t1Relevance = t1.getSearchScore(searchFor);
					int t2Relevance = t2.getSearchScore(searchFor);
					return t1Relevance > t2Relevance ? -1 : 1;
				}
				
			});
		}
		
		return hits;
	}

}
